package xfuzzy.xfghl.evaluation;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Vector;

import xfuzzy.lang.Specification;
import xfuzzy.lang.XflException;
import xfuzzy.xfghl.codification.DoubleChromosome;
import xfuzzy.xfghl.codification.Encoder;
import xfuzzy.xfghl.model.XfghlConfig;
import xfuzzy.xfhl.model.XfhlData;
import xfuzzy.xfhl.model.hierStruct.XfhlHierStruct;
import xfuzzy.xfhl.model.hierStruct.XfhlModule;
import xfuzzy.xfhl.model.spec.XfhlSpecification;
import xfuzzy.xfsl.model.XfslAlgorithm;
import xfuzzy.xfsl.model.XfslAlgorithmFactory;
import xfuzzy.xfsl.model.XfslConfig;
import xfuzzy.xfsl.model.XfslEndCondition;
import xfuzzy.xfsl.model.XfslErrorFunction;
import xfuzzy.xfsl.model.XfslThread;
import xfuzzy.xfsl.model.algorithm.Marquardt;
import xfuzzy.xfsl.model.algorithm.RProp;
import xfuzzy.xfsl.model.algorithm.XfslGradientBasedAlgorithm;

/**
 * Clase que representa el hilo de ejecucion de la evaluacion de un individuo
 * @author Alberto David Fernandez FErnandez
 * */

public class XfghlThread extends Observable implements Runnable {

	/**
	 * Lista de individuos a evaluar
	 * */
	private List<DoubleChromosome> chromosomes;
	
	/**
	 * Configuracion de la herramienta Xfghl
	 * */
	private XfghlConfig conxfghl;
	
	/**
	 * Objeto para el ajuste de la especificacion
	 * */
	private XfhlData data;
	
	/**
	 * Lista de individuos evaluados
	 * */
	private Vector<DoubleChromosome> evaluated;
	
	/**
	 * Constructor de la clase
	 * @param conf Configuracion de la herramienta Xfghl
	 * @param data Datos para el ajuste de la especificacion
	 * */

	public XfghlThread(XfghlConfig conf, XfhlData data) {

		this.conxfghl = conf;
		this.data = data;
		this.chromosomes = new ArrayList<DoubleChromosome>();
	}
	
	/**
	 * Constructor de la clase
	 * @param idThread Identificador del hilo
	 * @param conf Configuracion de la herramienta Xfghl
	 * @param data Datos para el ajuste de la especificacion
	 * @param chromosome Individuo a evualuar
	 * @param evaluated Individuos evaluados
	 * */

	public XfghlThread(XfghlConfig conf, XfhlData data, DoubleChromosome chromosome, Vector<DoubleChromosome> evaluated) {

		this(conf, data);
		this.chromosomes.add(chromosome);
		this.evaluated = evaluated;
	}

	public void addChromosome(DoubleChromosome chromosome) {

		this.chromosomes.add(chromosome);
	}
	
	/**
	 * Ejecucion del hilo
	 * */

	public void run() {

		XfhlModule modulo = null;
		XfhlHierStruct struc = null;
		DoubleChromosome chromosome = null, chromosomeAux = null;
		Specification spec = null;
		String codificacion = "";
		while (this.chromosomes.size() > 0) {
			chromosome = this.chromosomes.remove(0);
			// Si no se ha calculado el canonico, se calcula
			if (chromosome.getCanonic().isEmpty()) {
				codificacion = Encoder.generarCanonico(chromosome);
				chromosome.setCanonic(codificacion);
			} else {
				codificacion = chromosome.getCanonic();
			}
			// se genera la estructura jerarquica
			modulo = Encoder.decodeBackwardAnchuraSimple(chromosome);
			struc = new XfhlHierStruct(modulo, chromosome.getNumVariables() + chromosome.getNumModulos(), chromosome.getNumModulos());
			// Creamos la especificacion
			try {
				spec = XfhlSpecification.create(struc, conxfghl);
			} 
			catch (XflException e) {
				e.printStackTrace();
			}
			// Consultamos si se ha evaluado un individuo equivalente
			chromosomeAux = isEvaluated(chromosome);
			if (chromosomeAux == null)  {
				System.out.println("Evaluacion algoritmo");
				// Ajustamos la especificacion
				chromosome.setSpecification(spec);
				this.data.setSpec(spec);
				learning(spec);

				chromosome.setFitness(this.data.getFinalError());
				chromosome.setEvaluated(true);
				this.evaluated.add(chromosome);
				setChanged();
				notifyObservers(chromosome);
				clearChanged();
			} 
			else {
				chromosome.setSpecification(spec);
				chromosome.setFitness(chromosomeAux.getFitness());
				chromosome.setEvaluated(true);
			}
		}
	}
	
	/**
	 * Metodo que comprueba si un individuo ha sid evaluado anteriormente
	 * @param chromosome Individuo a comprobar
	 * @return DoubleChromosome Individuo evaluado equivalente 
	 * */
	
	private DoubleChromosome isEvaluated(DoubleChromosome chromosome)  {
		
		for (DoubleChromosome chromAux : this.evaluated)  {
			 if (chromAux.getCanonic().compareTo(chromAux.getCanonic()) == 0)
				 return chromAux;
		}
		return null;
	}

	/**
	 * Ajusta las salidas del sistema con el algoritmo de aprendizaje
	 * @param spec Especificacion a ajustar
	 */
	private void learning(Specification spec) {

		XfslThread lrnthread = new XfslThread(spec, createtXfslConfig());

		// Añadimos al objeto data como observador para notificarle los cambios
		lrnthread.addObserver(this.data);

		try {
			lrnthread.tuning();
		} catch (XflException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Crea una configuracion para la herramienta de ajuste con la configuracion
	 * de la herramienta Xfhl
	 * 
	 * @return La configuracion de la herramienta Xfsl
	 */
	private XfslConfig createtXfslConfig() {

		XfslConfig xfslConfig = new XfslConfig();

		// Parametros del algoritmo
		double param[] = { this.conxfghl.getIniUpd(), this.conxfghl.getIncFact(), this.conxfghl.getDecFact() };

		int alg = -1;
		int err = XfslErrorFunction.MEAN_SQUARE_ERROR;
		int end = XfslEndCondition.EPOCH;

		if (this.conxfghl.getAlgorithm() == 0) {
			alg = XfslAlgorithm.RPROP;

			RProp rprop;
			try {
				rprop = (RProp) XfslAlgorithmFactory.createAlgorithm(alg, param);
				// rprop.setOption(RProp.COMPUTE,
				// this.confXfhl.getPerturbation());
				rprop.setOption(RProp.COMPUTE, XfslGradientBasedAlgorithm.COMPUTE);
				xfslConfig = new XfslConfig();
				xfslConfig.trainingfile = this.conxfghl.getDataSetFile();
				xfslConfig.algorithm = rprop;
				xfslConfig.errorfunction = new XfslErrorFunction(err);
				xfslConfig.endcondition.setLimit(end, this.conxfghl.getnumIterAjust());

			} catch (XflException e) {
				e.printStackTrace();
			}
		} else if (this.conxfghl.getAlgorithm() == 1) {

			alg = XfslAlgorithm.MARQUARDT;
			Marquardt marq;
			try {
				marq = (Marquardt) XfslAlgorithmFactory.createAlgorithm(alg, param);
				// marq.setOption(Marquardt.COMPUTE,
				// this.confXfhl.getPerturbation());
				marq.setOption(Marquardt.COMPUTE, 0);
				xfslConfig = new XfslConfig();
				xfslConfig.trainingfile = this.conxfghl.getDataSetFile();
				xfslConfig.algorithm = marq;
				xfslConfig.errorfunction = new XfslErrorFunction(err);
				xfslConfig.endcondition.setLimit(end, this.conxfghl.getnumIterAjust());

			} catch (XflException e) {
				e.printStackTrace();
			}
		}
		return xfslConfig;
	}

	/**
	 * GETTERS AND SETTERS
	 * */
	public XfhlData getData() {

		return this.data;
	}
}
